package it.fuesi.expired.download;

import android.app.DownloadManager;
import android.app.DownloadManager.Query;
import android.app.DownloadManager.Request;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;

import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;


public class FuesiDownloadManager {

    private static final String TAG = FuesiDownloadManager.class.getName();

    private final Map<Long, DownloadCallback> activeDownloads = new ConcurrentHashMap<Long, DownloadCallback>();
    private final Context context;
    private final DownloadManager downloadManager;
    private String authCookieName;
    private String authCookieValue;
    
    private static FuesiDownloadManager instance;
    

    private FuesiDownloadManager(Context context) {
        this.context = context;
        downloadManager = (DownloadManager)context.getSystemService(Context.DOWNLOAD_SERVICE);
    }
    
    public static FuesiDownloadManager getInstance(Context context, String authCookieName, String authCookieValue){
    	getInstance(context);
    	instance.authCookieName = authCookieName;
    	instance.authCookieValue = authCookieValue;
    	return instance;
    }
    
    public static FuesiDownloadManager getInstance(Context context){
    	if(instance == null)
    		instance = new FuesiDownloadManager(context);
    	
    	return instance;
    }

    public void enqueue(Uri source, DownloadCallback callback) throws IOException {
        if (!isExternalStorageWritable()) {
            throw new IOException("Cannot write to external storage");
        }

        /* Ensure the destination directory already exists. */

        File destination = new File(context.getExternalFilesDir(
                                        callback.getDestinationDirType()), callback.getDestinationSubPath());
        destination.getParentFile().mkdirs();
        
        if(destination.exists()){
        	destination.delete();
        }

        Request request = new Request(source);
        request.setDestinationInExternalFilesDir(context, callback.getDestinationDirType(),
                callback.getDestinationSubPath());
        request.addRequestHeader("user-agent", "futuraula");
        request.addRequestHeader("Cookie",authCookieName+"="+authCookieValue);
        request.setTitle(callback.getTitle());
        request.setDescription(callback.getDescription());
        request.setNotificationVisibility(callback.getNotificationVisibility());

        long id = downloadManager.enqueue(request);
        activeDownloads.put(id, callback);

        callback.onStart(destination.getAbsolutePath());

        Log.v(TAG, String.format("Enqueued download with id %d", id));
    }

    private boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();
        return state.equals(Environment.MEDIA_MOUNTED);
    }

    public void cancel(Object object) {
        for (Entry<Long, DownloadCallback> entry : activeDownloads.entrySet()) {
            if (!entry.getValue().getObject().equals(object)) {
                continue;
            }

            downloadManager.remove(entry.getKey());

            DownloadCallback callback = activeDownloads.remove(entry.getKey());
            callback.onCancel();

            break;
        }
    }

    /**
     * Cancels all active downloads.
     */
    public void cancelAll() {
        for (Entry<Long, DownloadCallback> entry : activeDownloads.entrySet()) {
            downloadManager.remove(entry.getKey());

            DownloadCallback callback = activeDownloads.remove(entry.getKey());
            callback.onCancel();
        }
    }

    /**
     * Called once a download has completed. Responsible for updating the internal
     * state and pushing episode/podcast changes to the database.
     * @param id The download id.
     */
    public void downloadComplete(long id) {
        if (!activeDownloads.containsKey(id)) {
            return;
        }

        DownloadCallback callback = activeDownloads.remove(id);
        if (callback == null) {
            Log.w(TAG, String.format("No active download found for id %d", id));
            return;
        }

        if (!isDownloadSuccessful(id)) {
            Log.w(TAG, String.format("Download for id %d did not complete successfully (Reason: %d)",
                                     id, getDownloadFailureReason(id)));
            callback.onError();
            return;
        }

        Uri uri = downloadManager.getUriForDownloadedFile(id);
        Log.v(TAG, String.format("File %s downloaded successfully", uri.getPath()));

        callback.onFinish(uri);
    }

    private boolean isDownloadSuccessful(long id) {
        int status = getDownloadQueryAsInt(id, DownloadManager.COLUMN_STATUS);
        return (DownloadManager.STATUS_SUCCESSFUL == status);
    }

    private int getDownloadFailureReason(long id) {
        return getDownloadQueryAsInt(id, DownloadManager.COLUMN_REASON);
    }

    private int getDownloadQueryAsInt(long id, String column) {
        Query query = new Query();
        query.setFilterById(id);

        Cursor c = downloadManager.query(query);
        if (!c.moveToFirst()) {
            return -1;
        }

        int columnIndex = c.getColumnIndex(column);
        return c.getInt(columnIndex);
    }

    public interface DownloadCallback {
        void onStart(String path);
        void onCancel();
        void onError();
        void onFinish(Uri uri);

        String getDestinationDirType();
        String getDestinationSubPath();
        String getTitle();
        String getDescription();
        int getNotificationVisibility();
        Object getObject();
    }
}
